
package kuti.grp5;

import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import kuti.EWorldElementType;
import kuti.IChargingStation;
import kuti.IKuti;
import kuti.IObservableChargingStation;

/**
 * The charging station is an obstacle where the kuti cans recharge the energy.
 * 
 * @author Marc Schorderet <marc.schorderet@edu.hefr.ch>
 * @author Vincenzo Do <vincenzo.do@edu.hefr.ch>
 * 
 */

public class ChargingStation implements IChargingStation, IObservableChargingStation, IRadarElement {
    
    /**
     * Constants for the left side
     */
    
    public static final int LEFT_SIDE = 1;
    
    /**
     * Constants for the right side
     */
    
    public static final int RIGHT_SIDE = 2;
    
    /**
     * Constants for the top side
     */
    
    public static final int TOP_SIDE = 3;
    
    /**
     * Constants for the bottom side
     */
    
    public static final int BOTTOM_SIDE = 4;
    
    /**
     * Constants for the left-top corners
     */
    
    public static final int LEFT_TOP_CORNER = 1;
    
    /**
     * Constants for the left-bottom corners
     */
    
    public static final int LEFT_BOTTOM_CORNER = 2;
    
    /**
     * Constants for the right-top corners
     */
    
    public static final int RIGHT_TOP_CORNER = 3;
    
    /**
     * Constants for the right-bottom corners
     */
    
    public static final int RIGHT_BOTTOM_CORNER = 4;
    
    /**
     * ID of the next charging station
     */
    
    private static int next_id = 0;
    
    /**
     * ID of the current charging station
     */
    
    private int id;
    
    /**
     *  Tau value of the current charging station
     */
    
    private double tau;
    
    /**
     * Center point the charging station
     */
    
    private Point2D center;
    
    /**
     * Width of the charging station
     */
    
    private double width;
    
    /**
     * Height of the charging station
     */
    
    private double height;
    
    /**
     * line of the left side
     */
    
    private Line2D left;
    
    /**
     * Line of the right side
     */
    
    private Line2D right;
    
    /**
     * Line of the top side
     */
    
    private Line2D top;
    
    /**
     * Line of the bottom side
     */
    
    private Line2D bottom;
    
    /**
     * Rectangle of the charging station
     */
    
    private Rectangle2D rect;
    
    /**
     * Create a new charging station.
     * 
     * @param center    Center of the charging station
     * @param width     Wight of the charging station
     * @param height    Height of the charging station
     * @param tau       Tau of the charging station (power)
     * 
     */
    
    public ChargingStation(Point2D center, double width, double height, double tau) {
        
        // Check if the center is defined
        if(center == null){
            throw new IllegalArgumentException("The center of the charging station is not defined.");
        }
        
        // Check if the center values are valid
        if(Double.isNaN(center.getX()) || Double.isNaN(center.getY())){
            throw new IllegalArgumentException("The center values are not a number.");
        }
        
        // Check if the width not a number
        if(Double.isNaN(width)){
            throw new IllegalArgumentException("The width is not a numner");
        }
        
        // Check if the width is positif
        if(width < 0.0){
            throw new IllegalArgumentException("The width must be positif");
        }
        
        // Check if the height is not a number
        if(Double.isNaN(height)){
            throw new IllegalArgumentException("The height is not a numner");
        }
        
        // Check if the height is posifif
        if(height < 0.0){
            throw new IllegalArgumentException("The height must be positif");
        }
        
        // Check if the tau is not a number
        if(Double.isNaN(tau)){
            throw new IllegalArgumentException("The tau is not a numner");
        }
        
        // Check if the tau is between 0 and 100
        if(tau < 0.0 || tau > 100.0){
            throw new IllegalArgumentException("The tau must be between 0.0 and 100.0");
        }

        // Generate new ID
        this.id = next_id++;
        
        this.center = center;
        this.width = width;
        this.height = height;
        this.tau = tau;
        
        // Create points on the 4 corners
        Point2D leftBottom =  new Point2D.Double(center.getX() - width / 2, center.getY() - height / 2);
        Point2D leftTop =     new Point2D.Double(center.getX() - width / 2, center.getY() + height / 2);
        Point2D rightBottom = new Point2D.Double(center.getX() + width / 2, center.getY() - height / 2);
        Point2D rightTop =    new Point2D.Double(center.getX() + width / 2, center.getY() + height / 2);

        // Create the 4 sides
        this.left =   new Line2D.Double(leftBottom, leftTop);
        this.right =  new Line2D.Double(rightBottom, rightTop);
        this.top =    new Line2D.Double(leftTop, rightTop);
        this.bottom = new Line2D.Double(leftBottom, rightBottom);

        // Create the charging station rectangle
        this.rect = new Rectangle2D.Double(left.getX1(), left.getY1(), width, height);
        
    }

    /**
     * Get the center point
     * 
     * @return Returns the center point
     * 
     */
    
    @Override
    public Point2D center() {
        return center;
    }
    
    /**
     * Get the width
     * 
     * @return Returns the width
     * 
     */

    @Override
    public double width() {
        return width;
    }
    
    /**
     * Get the height
     * 
     * @return Returns the heught
     * 
     */

    @Override
    public double height() {
        return height;
    }
    
    /**
     * Get the element type
     * 
     * @return Retirms the element type
     * 
     */

    @Override
    public EWorldElementType getElementType() {
        return EWorldElementType.CHARGING_STATION;
    }
    
    /**
     * Get the ID
     * 
     * @return Returns the ID
     */

    @Override
    public int getId() {
        return id;
    }
    
    /**
     * Get the tau
     * 
     * @return Returns the tau 
     */

    @Override
    public double getTau() {
        return tau;
    }

    /**
     * This method returns if the charging station is on the kuti trajectory.
     *
     * @param kuti kuti trajectory (or radar trajectory)
     *
     * @return Returns true if the charging station intersects with the kuti
     * trajectory, else false.
     */
    
    @Override
    public boolean isIntersects(Line2D kuti) {
        return intersectsDistance(kuti) >= 0.0;
    }

    /**
     * This method returns the distance between the kuti center and the charging
     * station.
     *
     * If there is no intersection point, this method returns a negative value.
     * If the kuti center is on the charging station, the result is 0.0.
     *
     * @param kuti kuti trajectory (or radar trajectory)
     *
     * @return Returns the distance between the kuti center and the charging
     * station.
     */
    
    @Override
    public double intersectsDistance(Line2D kuti) {
        
        if(kuti == null){
            throw new IllegalArgumentException("The kuti line is not defined.");
        }
        
        // Check if the kuti center is in the charging station
        if (rect.contains(kuti.getP1())) {
            return 0.0;
        }

        Point2D iLeft = KutiTrajectory.intersectionLineLine(kuti, left);
        Point2D iRight = KutiTrajectory.intersectionLineLine(kuti, right);
        Point2D iTop = KutiTrajectory.intersectionLineLine(kuti, top);
        Point2D iBottom = KutiTrajectory.intersectionLineLine(kuti, bottom);

        double distance = Double.POSITIVE_INFINITY;
        boolean intersection = false;

        // Check the left side
        if (iLeft != null) {
            distance = Math.min(distance, iLeft.distance(kuti.getP1()));
            intersection = true;
        }

        // Check the right side
        if (iRight != null) {
            distance = Math.min(distance, iRight.distance(kuti.getP1()));
            intersection = true;
        }

        // Check the top side
        if (iTop != null) {
            distance = Math.min(distance, iTop.distance(kuti.getP1()));
            intersection = true;
        }

        // Check the bottom side
        if (iBottom != null) {
            distance = Math.min(distance, iBottom.distance(kuti.getP1()));
            intersection = true;
        }

        return (intersection) ? distance : Double.NEGATIVE_INFINITY;

    }
    
    /**
     * This method check if the kuti is on the charging station.
     * 
     * @param kuti      The kuti
     * @param radius    The radius of the kuti
     * 
     * @return Returns true if the kuti is on the charging station, else
     * returnes false.
     * 
     */
    
    public boolean isKutiOnChargingStation(IKuti kuti, double radius){
       
        if(kuti == null){
            throw new IllegalArgumentException("The kuti line is not defined.");
        }
        
        if(Double.isNaN(radius)){
            throw new IllegalArgumentException("The radius is not a numner.");
        }
        
        if(radius < 0.0){
            throw new IllegalArgumentException("The radius must be positif.");
        }
        
        // Check if the center of the kuti is on the charging station
        if(rect.contains(kuti.getCenterPosition())){
            return true;
        }
        
        // Check if the kuti intersects witch the left side of the cs.
        if(left.ptSegDist(kuti.getCenterPosition()) < radius){
            return true;
        }
        
        // Check if the kuti intersects witch the bottom side of the cs.
        if(bottom.ptSegDist(kuti.getCenterPosition()) < radius){
            return true;
        }
        
        // Check if the kuti intersects witch the right side of the cs.
        if(right.ptSegDist(kuti.getCenterPosition()) < radius){
            return true;
        }
        
        // Check if the kuti intersects witch the top side of the cs.
        if(top.ptSegDist(kuti.getCenterPosition()) < radius){
            return true;
        }
        
        return false;
    }
    
    /**
     * This method calculate the intersection area between the kuti and the
     * charging station.
     * 
     * @param kuti      The kuti
     * @param radius    The radius of the kuti
     * 
     * @return Returns the common area between the charging station and the
     * kuti.
     * 
     */
    
    public double commonAreaChargingStationKuti(IKuti kuti, double radius){
        
        if(kuti == null){
            throw new IllegalArgumentException("The kuti line is not defined.");
        }
        
        if(Double.isNaN(radius)){
            throw new IllegalArgumentException("The radius is not a numner.");
        }
        
        if(radius < 0.0){
            throw new IllegalArgumentException("The radius must be positif.");
        }
        
        // If kuti is not of the charging station, return 0.0
        if(!isKutiOnChargingStation(kuti, radius)){
            return 0.0;
        }
        
        // get the center of the kuti
        Point2D kutiCenter = kuti.getCenterPosition();
        
        // Check if the kuti intersects witch a side of the charging station
        boolean isIntersectTop =    top.ptSegDist(kutiCenter)    < radius;
        boolean isIntersectBottom = bottom.ptSegDist(kutiCenter) < radius;
        boolean isIntersectLeft =   left.ptSegDist(kutiCenter)   < radius;
        boolean isIntersectRight =  right.ptSegDist(kutiCenter)  < radius;
        
        // Check the position on the x and y axis
        boolean betweenTopAndBottom = (kutiCenter.getY() <= top.getY1() && kutiCenter.getY() >= bottom.getY1());
        boolean betweenLeftAndRight = (kutiCenter.getX() <= right.getX1() && kutiCenter.getX() >= left.getX1());
        
        // Check if a corner is in the kuti
        boolean isLeftTopIn =     kutiCenter.distance(left.getP2())  < radius;
        boolean isRightTopIn =    kutiCenter.distance(right.getP2()) < radius;
        boolean isLeftBottomIn =  kutiCenter.distance(left.getP1())  < radius;
        boolean isRightBottomIn = kutiCenter.distance(right.getP1()) < radius;
        
        // Calculate the chords (exterior side, not of the charging station)
        double chordLeft =    chordCircleArea(kutiCenter, radius, left,   !betweenLeftAndRight);
        double chordRight =   chordCircleArea(kutiCenter, radius, right,  !betweenLeftAndRight);
        double chordTop =     chordCircleArea(kutiCenter, radius, top,    !betweenTopAndBottom);
        double chordBottom =  chordCircleArea(kutiCenter, radius, bottom, !betweenTopAndBottom);
        
        // Calculate the total area of the kuti
        double totalCircleArea = radius * radius * Math.PI;
        double result = totalCircleArea;
        
        // Update values if the corners are in the kuti
        if(isLeftTopIn){ 
            double cornerArea = cornerArea(kutiCenter, radius, left.getP2(), LEFT_TOP_CORNER);
            result -= ( totalCircleArea - cornerArea - chordLeft - chordTop  );
        }
        
        if(isLeftBottomIn){
            double cornerArea = cornerArea(kutiCenter, radius, left.getP1(), LEFT_BOTTOM_CORNER);
            result -= ( totalCircleArea - cornerArea - chordLeft - chordBottom  );
        }

        if(isRightTopIn){
            double cornerArea = cornerArea(kutiCenter, radius, right.getP2(), RIGHT_TOP_CORNER);
            result -= ( totalCircleArea - cornerArea - chordRight - chordTop  );
        }

        if(isRightBottomIn){
            double cornerArea = cornerArea(kutiCenter, radius, right.getP1(), RIGHT_BOTTOM_CORNER);
            result -= ( totalCircleArea - cornerArea - chordRight - chordBottom  );
        }
        
        // Remove the exterior sides
        if(isIntersectTop)
            result -= chordTop;
        
        if(isIntersectBottom)
            result -= chordBottom;
        
        if(isIntersectLeft)
            result -= chordLeft;
        
        if(isIntersectRight)
            result -= chordRight;
        
        return result;
        
    }
    
    /**
     * This method returns the chord area of a cicle.
     * 
     * @param center    The center of the kuti
     * @param radius    The radius of the kuti
     * @param line      The intersection line
     * @param inverse   if inverse == true returns the chord area, else returns
     * the total area minus the chord area.
     * 
     * @return Returns the chord area of a circle.
     * 
     */
    
    private static double chordCircleArea(Point2D center, double radius, Line2D line, boolean inverse){
       
        if(center == null){
            throw new IllegalArgumentException("The center point is not defined.");
        }
        
        if(Double.isNaN(center.getX()) || Double.isNaN(center.getY())){
            throw new IllegalArgumentException("The center values are not a number");
        }
        
        if(Double.isNaN(radius)){
            throw new IllegalArgumentException("The radius is not a numner.");
        }
        
        if(radius < 0.0){
            throw new IllegalArgumentException("The radius must be positif.");
        }
        
        if(line == null){
            throw new IllegalArgumentException("The line is not defined.");
        }
        
        if(Double.isNaN(line.getX1()) || Double.isNaN(line.getX2()) || Double.isNaN(line.getY1()) || Double.isNaN(line.getY2())){
            throw new IllegalArgumentException("The line values are not a number.");
        }
        
        // calculate the height
        double h = radius - (line.ptLineDist(center));
        
        // If there are no intersection
        if(h <= 0.0)
            return 0.0;
        
        // calculate the area
        double chordLenght = 2 * Math.sqrt(radius * radius - radius + h);
        double area = h / ( 6 * chordLenght ) * (3 * h * h + 4 * chordLenght * chordLenght);
        
        return (inverse) ? Math.PI * radius * radius - area : area;
        
    }
    
    /**
     * This method calculate the area of a corner intersects witch the kuti.
     * 
     * @param center        The center of the kuti
     * @param radius        The radius of the kuti
     * @param cornerPoint   The corner point of the charging station
     * @param corner        The corner of the charging station
     *                      LEFT_TOP_CORNER, LEFT_BOTTOM_CORNER, 
     *                      RIGHT_TOP_CORNER, RIGHT_BOTTOM_CORNER
     * 
     * @return Returns the corner area.
     * 
     */
    
    private double cornerArea(Point2D center, double radius, Point2D cornerPoint, int corner){
        
        if(center == null){
            throw new IllegalArgumentException("The center point is not defined.");
        }
        
        if(Double.isNaN(center.getX()) || Double.isNaN(center.getY())){
            throw new IllegalArgumentException("The center values are not a number");
        }
        
        if(Double.isNaN(radius)){
            throw new IllegalArgumentException("The radius is not a numner.");
        }
        
        if(radius < 0.0){
            throw new IllegalArgumentException("The radius must be positif.");
        }
        
        if(cornerPoint == null){
            throw new IllegalArgumentException("The corner is not defined.");
        }
        
        if(Double.isNaN(cornerPoint.getX()) || Double.isNaN(cornerPoint.getY())){
            throw new IllegalArgumentException("The corner point values are not a number.");
        }
        
        Point2D i1 = null, i2 = null;
        
        switch(corner){
            case LEFT_TOP_CORNER:
                i1 = getIntersectionPointsLineCircle(center, radius, top,  RIGHT_SIDE);
                i2 = getIntersectionPointsLineCircle(center, radius, left, BOTTOM_SIDE);
                break;
            case LEFT_BOTTOM_CORNER:
                i1 = getIntersectionPointsLineCircle(center, radius, bottom,  RIGHT_SIDE);
                i2 = getIntersectionPointsLineCircle(center, radius, left, TOP_SIDE);
                break;
            case RIGHT_TOP_CORNER:
                i1 = getIntersectionPointsLineCircle(center, radius, top,  LEFT_SIDE);
                i2 = getIntersectionPointsLineCircle(center, radius, right, BOTTOM_SIDE);
                break;
            case RIGHT_BOTTOM_CORNER:
                i1 = getIntersectionPointsLineCircle(center, radius, bottom,  LEFT_SIDE);
                i2 = getIntersectionPointsLineCircle(center, radius, right, TOP_SIDE);
                break;
            default:
                return Double.NaN;
        }
        
        // Calculate the area
        double triangle = cornerPoint.distance(i1) * cornerPoint.distance(i2) /2;
        double innerChord = chordCircleArea(center, radius, new Line2D.Double(i1, i2), false);
        
        return triangle + innerChord;
        
    }
    
    /**
     * This method calculate on of the intersects points from a cercle witch a
     * rechtangle. This simulate the intersection between a kuti and the
     * charging station. It returns just one intersection point, but we can
     * choose it on the horisontal or verttical axis.
     * 
     * @param center    The center of the kuti
     * @param radius    The radius of the kuti
     * @param line      The intersection line. Lines can be horizontal or 
     * vertical, so that we implement the intersections with the vertical lines 
     * and horisontales. If the line is neither one nor the other, the method 
     * return null.
     * @param side      The side can specified the return point
     * 
     * LEFT_SIDE    :   Returns the left point of the horizontal axis
     * RIGHT_SIDE   :   Returns the right point of the horizontal axis
     * TOP_SIDE     :   Returns the top point of the vertical axis
     * BOTTOM_SIDE  :   Returns the bottom point of the vertical axis
     * 
     * @return Returns the intersection point between the kuti and the charging
     * station. If the line is not vertical or horinzontal, it returns null. And
     * if it has no intersection point, it return null too.
     * 
     */
    
    private static Point2D getIntersectionPointsLineCircle(Point2D center, double radius, Line2D line, int side){
        
        // If there is no intersection point.
        if(line.ptLineDist(center) > radius){
            return null;
         }
        
        // If the line is vertical
        if(line.getX1() == line.getX2()){
            
            // If the distance of the line and the center is exacly the radius
            // distance, it returns on intersecton point
            if(line.ptLineDist(center) == radius){
                
                // Calculate the x and y coordinates
                double x = line.getX1();
                double y = Math.sqrt(radius * radius - (x - center.getX()) * (x - center.getX())) + center.getY();

                return new Point2D.Double(x, y);

            }
            
            // Calculate the x and y coordinates
            double x = line.getX1();
            double y1 = Math.sqrt(radius * radius - (x - center.getX()) * (x - center.getX())) + center.getY();
            double y2 = -Math.sqrt(radius * radius - (x - center.getX()) * (x - center.getX())) + center.getY();
            
            
            if(side == TOP_SIDE)
                // Returns the point of the top
                return new Point2D.Double(x, Math.max(y1, y2));
            else
                // Returns the point of the bottom
                return new Point2D.Double(x, Math.min(y1, y2));

        }else if(line.getY1() == line.getY2()){
            // If the line is horizontal
            
            // If the distance of the line and the center is exacly the radius
            // distance, it returns on intersecton point
            if(line.ptLineDist(center) == radius){

                // Calculate the x and y coordinates
                double y = line.getY1();
                double x = Math.sqrt(radius * radius - (y - center.getY()) * (y - center.getY())) + center.getX();

                return new Point2D.Double(x, y);

            }
            
            // Calculate the x and y coordinates
            double y = line.getY1();
            double x1 = Math.sqrt(radius * radius - (y - center.getY()) * (y - center.getY())) + center.getX();
            double x2 = -Math.sqrt(radius * radius - (y - center.getY()) * (y - center.getY())) + center.getX();
            if(side == RIGHT_SIDE)
                // Returns the point of the right
                return new Point2D.Double(Math.max(x1, x2), y);
            else
                // Returns the point of the left
                return new Point2D.Double(Math.min(x1, x2), y);
            
        }else{
            // If the line is not vertical and not horizontal
            return null;
        }
        
    }
    
    /**
     * Get the rectangle of the charging station
     * 
     * @return Returns the rectangle of the charging station
     */
    
    public Rectangle2D getRectangle(){
        return rect;
    }
    
}
